#include "Sprite.h"
#include "D3D11Renderer.h"

#include "../SimpleVertexShader.csh"
#include "../SimpleGeometryShader.csh"
#include "../SimplePixelShader.csh"

#include <stdlib.h>

CComPtr<ID3D11VertexShader>		Sprite::vertexShader;
CComPtr<ID3D11GeometryShader>	Sprite::geometryShader;
CComPtr<ID3D11PixelShader>		Sprite::pixelShader;

CComPtr<ID3D11SamplerState>		Sprite::samplerState;
CComPtr<ID3D11Buffer>			Sprite::vertexBuffer;
CComPtr<ID3D11InputLayout>		Sprite::inputLayout;

TextureManager					Sprite::textures;
CComPtr<ID3D11Buffer>			Sprite::screenGeometryCBuffer;
CComPtr<ID3D11Buffer>			Sprite::planeGeometryCBuffer;
CComPtr<ID3D11Buffer>			Sprite::srcRectGeometryCBuffer;
CComPtr<ID3D11Buffer>			Sprite::colorPixelCBuffer;
ScreenCBuffer					Sprite::screenCBuffer;
PlaneCBuffer					Sprite::planeCBuffer;
ColorCBuffer					Sprite::colorCBuffer;
SrcRectCBuffer					Sprite::srcRectCBuffer;
vector<RenderCall>				Sprite::renderCalls;

Sprite::Sprite()
{
	
}

Sprite::Sprite(const Sprite&)
{

}

Sprite::~Sprite()
{
	
}

void Sprite::Initialize(int _screenWidth, int _screenHeight)
{
	screenCBuffer.screenWidthHeight.x = (float)_screenWidth;
	screenCBuffer.screenWidthHeight.y = (float)_screenHeight;
	HRESULT hr;

	D3D11_BUFFER_DESC vBufferDesc;
	ZeroMemory(&vBufferDesc, sizeof(D3D11_BUFFER_DESC));
	vBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	vBufferDesc.ByteWidth = sizeof(XMFLOAT3);
	vBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	vBufferDesc.CPUAccessFlags = 0;

	XMFLOAT3 verts[1];
	verts[0] = XMFLOAT3(0.0f, 0.0f, 0.0f);

	D3D11_SUBRESOURCE_DATA vData;
	ZeroMemory(&vData, sizeof(D3D11_SUBRESOURCE_DATA));
	vData.pSysMem = &verts[0];

	hr = D3D11Renderer::d3dDevice->CreateBuffer(&vBufferDesc, &vData, &vertexBuffer);
	unsigned int stride;
	unsigned int offset;

	//Set the vertex buffer stride and offset
	stride = sizeof(XMFLOAT3);
	offset = 0;
	D3D11Renderer::d3dImmediateContext->IASetVertexBuffers(0, 1, &vertexBuffer.p, &stride, &offset);

	D3D11_BUFFER_DESC cBufferDesc;
	ZeroMemory(&cBufferDesc, sizeof(D3D11_BUFFER_DESC));
	cBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
	cBufferDesc.ByteWidth = sizeof(ScreenCBuffer);
	cBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	cBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

	hr = D3D11Renderer::d3dDevice->CreateBuffer(&cBufferDesc, NULL, &screenGeometryCBuffer);
	D3D11Renderer::d3dImmediateContext->GSSetConstantBuffers(0, 1, &screenGeometryCBuffer.p);
	cBufferDesc.ByteWidth = sizeof(PlaneCBuffer);
	hr = D3D11Renderer::d3dDevice->CreateBuffer(&cBufferDesc, NULL, &planeGeometryCBuffer);
	D3D11Renderer::d3dImmediateContext->GSSetConstantBuffers(1, 1, &planeGeometryCBuffer.p);
	cBufferDesc.ByteWidth = sizeof(SrcRectCBuffer);
	hr = D3D11Renderer::d3dDevice->CreateBuffer(&cBufferDesc, NULL, &srcRectGeometryCBuffer);
	D3D11Renderer::d3dImmediateContext->GSSetConstantBuffers(2, 1, &srcRectGeometryCBuffer.p);
	cBufferDesc.ByteWidth = sizeof(ColorCBuffer);
	hr = D3D11Renderer::d3dDevice->CreateBuffer(&cBufferDesc, NULL, &colorPixelCBuffer);
	D3D11Renderer::d3dImmediateContext->PSSetConstantBuffers(0, 1, &colorPixelCBuffer.p);

	hr = D3D11Renderer::d3dDevice->CreateVertexShader(SimpleVertexShader, sizeof(SimpleVertexShader), NULL, &vertexShader);
	hr = D3D11Renderer::d3dDevice->CreateGeometryShader(SimpleGeometryShader, sizeof(SimpleGeometryShader), NULL, &geometryShader);
	hr = D3D11Renderer::d3dDevice->CreatePixelShader(SimplePixelShader, sizeof(SimplePixelShader), NULL, &pixelShader);

	D3D11Renderer::d3dImmediateContext->VSSetShader(vertexShader, NULL, 0);
	D3D11Renderer::d3dImmediateContext->GSSetShader(geometryShader, NULL, 0);
	D3D11Renderer::d3dImmediateContext->PSSetShader(pixelShader, NULL, 0);

	D3D11_SAMPLER_DESC samplerDesc;
	samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.MipLODBias = 0.0f;
	samplerDesc.MaxAnisotropy = 16;
	samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
	samplerDesc.BorderColor[0] = 0;
	samplerDesc.BorderColor[1] = 0;
	samplerDesc.BorderColor[2] = 0;
	samplerDesc.BorderColor[3] = 0;
	samplerDesc.MinLOD = -D3D11_FLOAT32_MAX;
	samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;
	hr = D3D11Renderer::d3dDevice->CreateSamplerState(&samplerDesc, &samplerState);

	D3D11_INPUT_ELEMENT_DESC polygonLayout;
	polygonLayout.SemanticName = "POSITION";
	polygonLayout.SemanticIndex = 0;
	polygonLayout.Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout.InputSlot = 0;
	polygonLayout.AlignedByteOffset = 0;
	polygonLayout.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout.InstanceDataStepRate = 0;
	hr = D3D11Renderer::d3dDevice->CreateInputLayout(&polygonLayout, 1, 
		SimpleVertexShader,
		sizeof(SimpleVertexShader), &inputLayout);
}

void Sprite::Render(int _textureID, float _posX, float _posY, float _scaleX, float _scaleY, float _rotation, RECT _srcRect, float _depth, XMFLOAT4 _color)
{
	colorCBuffer.color = _color;
	planeCBuffer.widthHeightLocation.x = textures.GetTextureWidth(_textureID);
	planeCBuffer.widthHeightLocation.y = textures.GetTextureHeight(_textureID);
	planeCBuffer.widthHeightLocation.z = _posX;
	planeCBuffer.widthHeightLocation.w = _posY;
	planeCBuffer.scaleRotationDepth.x = _scaleX;
	planeCBuffer.scaleRotationDepth.y = _scaleY;
	planeCBuffer.scaleRotationDepth.z = XMConvertToRadians(_rotation);
	planeCBuffer.scaleRotationDepth.w = _depth;

	srcRectCBuffer.srcRect.x = (float)_srcRect.left;
	srcRectCBuffer.srcRect.y = (float)_srcRect.top;
	srcRectCBuffer.srcRect.z = (float)_srcRect.right;
	srcRectCBuffer.srcRect.w = (float)_srcRect.bottom;

	HRESULT hr = 0;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	D3D11Renderer::d3dImmediateContext->Map(screenGeometryCBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	memcpy(mappedResource.pData, &screenCBuffer, sizeof(ScreenCBuffer));
	D3D11Renderer::d3dImmediateContext->Unmap(screenGeometryCBuffer, 0);

	D3D11Renderer::d3dImmediateContext->Map(planeGeometryCBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	memcpy(mappedResource.pData, &planeCBuffer, sizeof(PlaneCBuffer));
	D3D11Renderer::d3dImmediateContext->Unmap(planeGeometryCBuffer, 0);

	D3D11Renderer::d3dImmediateContext->Map(srcRectGeometryCBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	memcpy(mappedResource.pData, &srcRectCBuffer, sizeof(SrcRectCBuffer));
	D3D11Renderer::d3dImmediateContext->Unmap(srcRectGeometryCBuffer, 0);

	D3D11Renderer::d3dImmediateContext->Map(colorPixelCBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	memcpy(mappedResource.pData, &colorCBuffer, sizeof(ColorCBuffer));
	D3D11Renderer::d3dImmediateContext->Unmap(colorPixelCBuffer, 0);

	ID3D11ShaderResourceView* srv = textures.GetTexture(_textureID);
	D3D11Renderer::d3dImmediateContext->PSSetShaderResources(0, 1, &srv);

	D3D11Renderer::d3dImmediateContext->PSSetSamplers(0, 1, &samplerState.p);
	
	D3D11Renderer::d3dImmediateContext->IASetInputLayout(inputLayout);
	
	D3D11Renderer::d3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);

	D3D11Renderer::d3dImmediateContext->Draw(1, 0);
}

void Sprite::AddRenderCall(int _textureID, float _posX, float _posY, float _scaleX, float _scaleY, float _rotation, RECT _srcRect, float _depth, XMFLOAT4 _color)
{
	RenderCall tempRenderCall;
	tempRenderCall.textureID = _textureID;
	tempRenderCall.color = _color;
	tempRenderCall.depth = _depth;
	tempRenderCall.posX = _posX;
	tempRenderCall.posY = _posY;
	tempRenderCall.rotation = _rotation;
	tempRenderCall.scaleX = _scaleX;
	tempRenderCall.scaleY = _scaleY;
	tempRenderCall.srcRect = _srcRect;

	renderCalls.push_back(tempRenderCall);
}

void Sprite::RenderFrame()
{
	if(renderCalls.size() == 0)
	{
		return;
	}

	if(renderCalls.size() > 1)
	{
		qsort((void*)&renderCalls[0], renderCalls.size(), sizeof(RenderCall), CompareDepth);
	}

	unsigned int numRenders = renderCalls.size();

	RenderCall callToRender;
	while(renderCalls.size() > 0)
	{
		callToRender = renderCalls[renderCalls.size() - 1];

		Render(callToRender.textureID, callToRender.posX, callToRender.posY, callToRender.scaleX, callToRender.scaleY,
			callToRender.rotation, callToRender.srcRect, callToRender.depth, callToRender.color);

		renderCalls.pop_back();
	}
}

int Sprite::AddImage(WCHAR* _fileName)
{
	return textures.AddTexture(D3D11Renderer::d3dDevice, _fileName);
}

int Sprite::CompareDepth(const void* _first, const void* _second)
{
	RenderCall first = *(RenderCall*)_first;
	RenderCall second = *(RenderCall*)_second;

	if(first.depth > second.depth)
	{
		return 1;
	}
	else if(first.depth < second.depth)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}